#ifndef __M_SINK_H__
#define __M_SINK_H__
#include "util.hpp"      // 通用工具类(包含文件操作等工具函数)
#include "message.hpp"   // 日志消息结构
#include "formatter.hpp" // 日志格式化器
#include <memory>        // 智能指针支持
#include <mutex>         // 互斥锁(用于线程安全)

namespace zdflog
{

    /**
     * @brief 日志输出目标基类
     * 定义了日志输出的接口，所有具体输出目标(如控制台、文件)都继承自此类
     */
    class LogSink
    {
    public:
        using ptr = std::shared_ptr<LogSink>; // 智能指针类型定义

        LogSink() {}          // 默认构造函数
        virtual ~LogSink() {} // 虚析构函数，确保派生类资源正确释放

        /**
         * @brief 纯虚函数，输出日志数据
         * @param data 日志数据的字符指针
         * @param len 日志数据的长度(字节数)
         */
        virtual void log(const char *data, size_t len) = 0;
    };

    /**
     * @brief 标准输出流日志输出目标
     * 将日志数据输出到标准输出(控制台)
     */
    class StdoutSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<StdoutSink>; // 智能指针类型定义

        StdoutSink() = default; // 默认构造函数

        /**
         * @brief 输出日志数据到标准输出
         * @param data 日志数据的字符指针
         * @param len 日志数据的长度(字节数)
         */
        void log(const char *data, size_t len)
        {
            std::cout.write(data, len); // 使用cout写入数据
        }
    };

    /**
     * @brief 文件日志输出目标
     * 将日志数据输出到指定的文件中
     */
    class FileSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<FileSink>; // 智能指针类型定义

        /**
         * @brief 构造函数
         * @param filename 日志文件路径
         */
        FileSink(const std::string &filename) : _filename(filename)
        {
            // 创建文件所在的目录(若不存在)
            util::file::create_directory(util::file::path(filename));
            // 以二进制模式和追加模式打开文件
            _ofs.open(_filename, std::ios::binary | std::ios::app);
            assert(_ofs.is_open()); // 确保文件成功打开
        }

        /**
         * @brief 获取当前日志文件路径
         * @return 日志文件路径字符串
         */
        const std::string &file() { return _filename; }

        /**
         * @brief 输出日志数据到文件
         * @param data 日志数据的字符指针
         * @param len 日志数据的长度(字节数)
         */
        void log(const char *data, size_t len)
        {
            _ofs.write((const char *)data, len); // 写入文件
            // 检查写入状态，失败时输出提示
            if (_ofs.good() == false)
            {
                std::cout << "日志输出文件失败！\n";
            }
        }

    private:
        std::string _filename; // 日志文件路径
        std::ofstream _ofs;    // 文件输出流对象
    };

    /**
     * @brief 滚动文件日志输出目标
     * 当日志文件达到指定大小后，自动切换到新文件(滚动生成新文件)
     */
    class RollSink : public LogSink
    {
    public:
        using ptr = std::shared_ptr<RollSink>; // 智能指针类型定义

        /**
         * @brief 构造函数
         * @param basename 日志文件基础名称(用于生成滚动文件的前缀)
         * @param max_fsize 单个日志文件的最大大小(字节)
         */
        RollSink(const std::string &basename, size_t max_fsize) : _basename(basename), _max_fsize(max_fsize), _cur_fsize(0)
        {
            // 创建文件所在的目录(若不存在)
            util::file::create_directory(util::file::path(basename));
        }

        /**
         * @brief 输出日志数据到滚动文件
         * 自动检查当前文件大小，超过阈值时切换到新文件
         * @param data 日志数据的字符指针
         * @param len 日志数据的长度(字节数)
         */
        void log(const char *data, size_t len)
        {
            initLogFile();         // 初始化日志文件(检查是否需要滚动)
            _ofs.write(data, len); // 写入文件

            // 检查写入状态，失败时输出提示
            if (_ofs.good() == false)
            {
                std::cout << "日志输出文件失败！\n";
            }

            _cur_fsize += len; // 更新当前文件大小
        }

    private:
        /**
         * @brief 初始化日志文件(检查并处理文件滚动)
         * 若文件未打开或已达到最大大小，则创建新文件
         */
        void initLogFile()
        {
            // 条件：文件未打开 或 当前文件大小超过最大限制
            if (_ofs.is_open() == false || _cur_fsize >= _max_fsize)
            {
                _ofs.close();                                      // 关闭当前文件
                std::string name = createFilename();               // 生成新文件名
                _ofs.open(name, std::ios::binary | std::ios::app); // 打开新文件
                assert(_ofs.is_open());                            // 确保新文件成功打开
                _cur_fsize = 0;                                    // 重置当前文件大小计数
                return;
            }
            return;
        }

        /**
         * @brief 生成滚动文件的名称
         * 基于基础名称和当前时间生成唯一文件名
         * @return 新的日志文件名
         */
        std::string createFilename()
        {
            time_t t = time(NULL); // 获取当前时间戳
            struct tm lt;
            localtime_r(&t, &lt); // 转换为本地时间

            std::stringstream ss;
            ss << _basename; // 基础名称
            // 追加时间信息(年、月、日、时、分、秒)，确保唯一性
            ss << lt.tm_year + 1900; // 年份(+1900是因为tm_year从1900开始计数)
            ss << lt.tm_mon + 1;     // 月份(+1是因为tm_mon从0开始)
            ss << lt.tm_mday;        // 日
            ss << lt.tm_hour;        // 时
            ss << lt.tm_min;         // 分
            ss << lt.tm_sec;         // 秒
            ss << ".log";            // 文件后缀
            return ss.str();
        }

    private:
        std::string _basename; // 日志文件基础名称
        std::ofstream _ofs;    // 文件输出流对象
        size_t _max_fsize;     // 单个文件最大大小(字节)
        size_t _cur_fsize;     // 当前文件大小(字节)
    };

    /**
     * @brief 日志输出目标工厂类
     * 提供创建各种LogSink实例的静态方法，封装对象创建逻辑
     */
    class SinkFactory
    {
    public:
        /**
         * @brief 创建指定类型的LogSink实例
         * @tparam SinkType LogSink的派生类型(如StdoutSink、FileSink等)
         * @tparam Args 构造SinkType所需的参数类型
         * @param ... 构造SinkType的参数
         * @return 新创建的LogSink智能指针
         */
        template <typename SinkType, typename... Args>
        static LogSink::ptr create(Args &&...args)
        {
            // 使用完美转发构造Sink实例并返回智能指针
            return std::make_shared<SinkType>(std::forward<Args>(args)...);
        }
    };

}
#endif